/*
 * OpenParts
 * A dynamic-loading components framework for GWT
 * Copyright (C) 2011 Christophe Bouthier  [chris{AT}binary-gastronome{DOT}fr]
 *
 * This work is partially based on work I have done at INRIA (http://www.inria.fr) 
 * in the context of the Qualipso European Project (http://qualipso.org/),
 * The work done at INRIA is Copyright (C) 2006-2011 INRIA
 *
 * This work is distributed under the LGPL version 3
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License version 3
 * as published by the Free Software Foundation. See the GNU
 * Lesser General Public License in LGPL.txt for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */
package fr.openparts.OpenParts.test.gwt.tests;

import com.google.gwt.dom.client.Element;
import com.google.gwt.junit.client.GWTTestCase;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.ui.RootPanel;

import fr.openparts.OpenParts.client.OPBinding;
import fr.openparts.OpenParts.client.OPPartLoadedCallback;
import fr.openparts.OpenParts.client.OPPartLoadingRequest;
import fr.openparts.OpenParts.client.OPPartsLoader;
import fr.openparts.OpenParts.client.OPPartsLoaderBinding;
import fr.openparts.OpenParts.client.OPShell;

/**
 * GWT tests for OPPartsLoader.
 * 
 * @author Christophe Bouthier [chris{AT}binary-gastronome{DOT}fr]
 * @creation.date 25 November 2010
 */
public class OPPartsLoaderTestGwt extends GWTTestCase {

    private static final String SERVICE_NAME = "dummyService_partloader";
    private static final int TIMER_TIMEOUT = 5500;
    private static final int TEST_TIMEOUT = 6000;
    
    private OPPartsLoaderBinding binding;
    private OPPartLoadedCallback callback;
    private OPPartLoadingRequest request;
    
    private int onSuccessCalled = 0;
    private int onTimeoutCalled = 0;
    
    /**
     * Return the name of the tested GWT Module.
     * 
     * @see com.google.gwt.junit.client.GWTTestCase#getModuleName()
     */
    @Override
    public final String getModuleName() {
        return "fr.openparts.OpenParts.test.gwt.TestOpenParts";
    }
    
    /**
     * Called at the start of each test to set up the part.
     * 
     * @see com.google.gwt.junit.client.GWTTestCase#gwtSetUp()
     */
    @Override
    protected void gwtSetUp() throws Exception {
        super.gwtSetUp();
        OPPartsLoader.bind();
        binding = getPartsLoaderBinding(OPShell.opBindingSharedInstance());
        
        callback = new OPPartLoadedCallback() {
            @Override
            public void onTimeout() {
                ++onTimeoutCalled;
            }

            @Override
            public void onSuccess() {
                ++onSuccessCalled;
            }
        };
        request = OPPartLoadingRequest.partLoadingRequestFactory(SERVICE_NAME, callback);
    }

    /**
     * Called at the end of each test to clean up the mess.
     * 
     * @see com.google.gwt.junit.client.GWTTestCase#gwtTearDown()
     */
    @Override
    protected final void gwtTearDown() throws Exception {
        super.gwtTearDown();
        unbindOPPartsLoader(OPShell.opBindingSharedInstance());
        removeScriptElement();
    }

    /**
     * Test binding the parts loader. Should get something not null.
     */
    public final void testBinding() {
        assertNotNull("There should be a parts loader now", binding);
        assertTrue("There should be an execute loading request method", isThereExecuteLoadingRequestMethod(binding));
        assertTrue("There should be a notify loading completed method", isThereNotifyLoadingCompletedMethod(binding));
        assertTrue("There should be a is part loaded method", isThereIsPartLoadedMethod(binding));
    }
    
    /**
     * Test loading a script. Should find the corresponding script element in the page.
     */
    public final void testLoadingScript() {
        binding.executeLoadingRequest(request);

        final Element scriptElement = DOM.getElementById(OPShell.partLoaderIdForService(SERVICE_NAME));
        assertNotNull("There is no element with the correct id", scriptElement);
        assertEquals("The element is not a script", "script", scriptElement.getTagName().toLowerCase());
        assertEquals("The script doesn't have a javascript type", "text/javascript", scriptElement.getAttribute("type"));
        assertEquals("The script doesn't have a javascript language", "javascript", scriptElement.getAttribute("language"));
        assertEquals("The script doesn't have the correct URL", OPShell.partLoaderUrlForService(SERVICE_NAME), scriptElement.getAttribute("src"));
    }
    
    /**
     * Test removing a script. Should not find the corresponding script element in the page.
     */
    public final void testRemovingScript() {
        binding.executeLoadingRequest(request);

        final Element scriptElement = DOM.getElementById(OPShell.partLoaderIdForService(SERVICE_NAME));
        assertNotNull("There is no element with the correct id", scriptElement);
        assertEquals("The element is not a script", "script", scriptElement.getTagName().toLowerCase());
        assertEquals("The script doesn't have a javascript type", "text/javascript", scriptElement.getAttribute("type"));
        assertEquals("The script doesn't have a javascript language", "javascript", scriptElement.getAttribute("language"));
        assertEquals("The script doesn't have the correct URL", OPShell.partLoaderUrlForService(SERVICE_NAME), scriptElement.getAttribute("src"));
        
        Timer timer = new Timer() {
            public void run() {
                final Element scriptElement2 = DOM.getElementById(OPShell.partLoaderIdForService(SERVICE_NAME));
                assertNull("There should not be any element with this id", scriptElement2);
                finishTest();
            }
        };
        timer.schedule(TIMER_TIMEOUT);
        delayTestFinish(TEST_TIMEOUT);
    }
    
    /**
     * Test executing a loading request which is successful. Should call the request callback onSuccess.
     */
    public final void testExecutingLoadingRequestSuccess() {
        assertEquals("onSuccess should not have been called yet", 0, onSuccessCalled);
        assertEquals("onTimeout should not have been called yet", 0, onTimeoutCalled);
        binding.executeLoadingRequest(request);
        binding.notifyLoadingCompleted(SERVICE_NAME);
        assertEquals("onSuccess should have been called now", 1, onSuccessCalled);
        assertEquals("onTimeout should still not have been called", 0, onTimeoutCalled);
    }

    /**
     * Test executing a loading request which is timed out. Should call the request callback onTimeout.
     */
    public final void testExecutingLoadingRequestTimeout() {
        assertEquals("onSuccess should not have been called yet", 0, onSuccessCalled);
        assertEquals("onTimeout should not have been called yet", 0, onTimeoutCalled);
        binding.executeLoadingRequest(request);
        Timer timer = new Timer() {
            public void run() {
                assertEquals("onSuccess should still not have been called", 0, onSuccessCalled);
                assertEquals("onTimeout should have been called now", 1, onTimeoutCalled);
                finishTest();
            }
        };
        timer.schedule(TIMER_TIMEOUT);
        delayTestFinish(TEST_TIMEOUT);
    }
    
    /**
     * Test executing a loading request which time out, but which is successful after the timeout. Should call the request callback onTimeout, but NOT the
     * onSuccess.
     */
    public final void testExecutingLoadingRequestTimeoutSuccess() {
        assertEquals("onSuccess should not have been called yet", 0, onSuccessCalled);
        assertEquals("onTimeout should not have been called yet", 0, onTimeoutCalled);
        binding.executeLoadingRequest(request);
        Timer timer = new Timer() {
            public void run() {
                binding.notifyLoadingCompleted(SERVICE_NAME);
                assertEquals("onSuccess should still not have been called", 0, onSuccessCalled);
                assertEquals("onTimeout should have been called now", 1, onTimeoutCalled);
                finishTest();
            }
        };
        timer.schedule(TIMER_TIMEOUT);
        delayTestFinish(TEST_TIMEOUT);
    }

    /**
     * Test executing a loading request which get an incorrect notification before being successful. Should call the request callback onSuccess just once.
     */
    public final void testHandleSingleRequestSuccessBadNotif() {
        assertEquals("onSuccess should not have been called yet", 0, onSuccessCalled);
        assertEquals("onTimeout should not have been called yet", 0, onTimeoutCalled);
        binding.executeLoadingRequest(request);
        binding.notifyLoadingCompleted(SERVICE_NAME + "2");
        binding.notifyLoadingCompleted(SERVICE_NAME);
        assertEquals("onSuccess should have been called only once", 1, onSuccessCalled);
        assertEquals("onTimeout should still not have been called", 0, onTimeoutCalled);
    }
    
    /**
     * Test executing two times a loading request which is successful the first time. Should get the script element only once.
     */
    public final void testExecutingLoadingRequestSuccessTwoTimes() {
        Element scriptElement = DOM.getElementById(OPShell.partLoaderIdForService(SERVICE_NAME));
        assertNull("There should not be any element with this id yet", scriptElement);
        
        binding.executeLoadingRequest(request);
        binding.notifyLoadingCompleted(SERVICE_NAME);
        scriptElement = DOM.getElementById(OPShell.partLoaderIdForService(SERVICE_NAME));
        assertNotNull("There should be one element with this id now", scriptElement);
        
        binding.executeLoadingRequest(request);
        final int childCount = DOM.getChildCount(RootPanel.getBodyElement());
        int idCount = 0;
        for (int i = 0; i < childCount; i++) {
            Element element = DOM.getChild(RootPanel.getBodyElement(), i);
            String id = DOM.getElementAttribute((com.google.gwt.user.client.Element) element, "id");
            if (OPShell.partLoaderIdForService(SERVICE_NAME).equals(id)) {
                ++idCount;
            }
        }
        assertEquals("There should be only one script element with this id", 1, idCount);
    }

    /**
     * Test checking the state of a part successfully loaded. Should get true.
     */
    public final void testIsPartLoaded() {
        binding.executeLoadingRequest(request);
        binding.notifyLoadingCompleted(SERVICE_NAME);
        assertTrue("Past should be loaded", binding.isPartLoaded(SERVICE_NAME));
    }
    
    /**
     * Test checking the state of a part not yet loaded. Should get false.
     */
    public final void testIsPartNotLoaded() {
        assertFalse("Past should not be loaded yet", binding.isPartLoaded(SERVICE_NAME));
    }
    
    /**
     * Test checking the state of a part unsuccessfully loaded. Should get false.
     */
    public final void testIsPartUnsuccessfullyLoaded() {
        binding.executeLoadingRequest(request);
        Timer timer = new Timer() {
            public void run() {
                assertFalse("Past should not be loaded", binding.isPartLoaded(SERVICE_NAME));
                finishTest();
            }
        };
        timer.schedule(TIMER_TIMEOUT);
        delayTestFinish(TEST_TIMEOUT);
    }
    
    /**
     * Test checking the state of a part still loading. Should get false.
     */
    public final void testIsPartLoadingInProgress() {
        binding.executeLoadingRequest(request);
        assertFalse("Past should not be loaded yet", binding.isPartLoaded(SERVICE_NAME));
    }
    
    /**
     * Get the parts loader binding.
     * 
     * @param binding
     *            the general binding.
     * @return the parts loader binding.
     */
    private final native OPPartsLoaderBinding getPartsLoaderBinding(OPBinding opbinding) /*-{
        return opbinding.partsLoader;
    }-*/;

    /**
     * Check if there is a method for executing loading request in the given part loader binding
     * 
     * @param binding
     *            the part loader binding
     * @return true if the method is here
     */
    private final native boolean isThereExecuteLoadingRequestMethod(OPPartsLoaderBinding oplbinding) /*-{
        return (oplbinding.executeLoadingRequest != null);
    }-*/;

    /**
     * Check if there is a method for notifying when a part completed loading in the given part loader binding
     * 
     * @param binding
     *            the part loader binding
     * @return true if the method is here
     */
    private final native boolean isThereNotifyLoadingCompletedMethod(OPPartsLoaderBinding oplbinding) /*-{
        return (oplbinding.notifyLoadingCompleted != null);
    }-*/;
    
    /**
     * Check if there is a method to check if a part is loaded in the given part loader binding
     * 
     * @param binding
     *            the part loader binding
     * @return true if the method is here
     */
    private final native boolean isThereIsPartLoadedMethod(OPPartsLoaderBinding oplbinding) /*-{
        return (oplbinding.isPartLoaded != null);
    }-*/;
    
    /**
     * Unbind the OPPartsLoader.
     */
    private final native void unbindOPPartsLoader(final OPBinding opbinding) /*-{
        opbinding.partsLoader = null;
    }-*/;
    
    /**
     * Remove the script element
     */
    private final void removeScriptElement() {
        final Element scriptElement = DOM.getElementById(OPShell.partLoaderIdForService(SERVICE_NAME));
        if (scriptElement == null) {
            return;
        }

        DOM.removeChild((com.google.gwt.user.client.Element)scriptElement.getParentElement(), (com.google.gwt.user.client.Element)scriptElement);
    }

    /**
     * Check if assertions are enabled.
     * 
     * @return true if assertions are enabled, false otherwise
     */
    private final boolean assertionsAreEnabled() {
        try {
            assert false;
            return false;
        } catch (AssertionError e) {
            return true;
        }
    }
}
